package com.atguigu.gmall.activity.service.impl;

import com.atguigu.gmall.activity.mapper.SeckillGoodsMapper;
import com.atguigu.gmall.activity.service.SeckillGoodsService;
import com.atguigu.gmall.activity.util.CacheHelper;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.model.activity.OrderRecode;
import com.atguigu.gmall.model.activity.SeckillGoods;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author atguigu-mqx
 */
@Service
public class SeckillGoodsServiceImpl implements SeckillGoodsService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private SeckillGoodsMapper seckillGoodsMapper;

    @Override
    public List<SeckillGoods> findAll() {
        //  查询所有数据： hvals key
        List<SeckillGoods> seckillGoodsList = redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).values();

        return seckillGoodsList;
    }

    @Override
    public SeckillGoods getSeckillGoods(Long skuId) {
        // 获取数据 hget key field
        //  注意细节： field 的数据类型！
        SeckillGoods seckillGoods = (SeckillGoods) this.redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).get(skuId.toString());
        return seckillGoods;
    }

    @Override
    public void seckillOrder(Long skuId, String userId) {
        //  1. 判断状态位。 2. 判断用户是否已经下过订单
        //  3. 减少库存。如果减库存失败，通知其他兄弟节点  4. 将订单记录放入缓存！ 5.更新库存
        String state = (String) CacheHelper.get(skuId.toString());
        if (StringUtils.isEmpty(state) || "0".equals(state)){
            return;
        }
        //  判断用户是否已经下过订单 setnx 当key不存在的时候，生效！ 存在不生效！
        //  key = seckill:user:userId
        //  setnx seckill:user:userId skuId   【set key value ex timeout nx; 具有原子性！】
        Boolean flag = this.redisTemplate.opsForValue().setIfAbsent(RedisConst.SECKILL_USER + userId, skuId, RedisConst.SECKILL__TIMEOUT, TimeUnit.SECONDS);
        //  判断 flag = true 表示用户第一次下单， flag = false 表示用户已经下过订单了。
        if (!flag){
            return;
        }
        //  减库存： 商品的剩余库存在redis -- list 中！ 如果库存中有10件商品，执行以下代码就会变成9个！
        String skuIdExist = (String) this.redisTemplate.opsForList().rightPop(RedisConst.SECKILL_STOCK_PREFIX + skuId);
        //  判断
        if (StringUtils.isEmpty(skuIdExist)){
            //  如果skuIdExist 是空，则说明该商品没有库存了！ 同时，通知其他兄弟节点，改变状态位！
            this.redisTemplate.convertAndSend("seckillpush",skuId+":0");
            return;
        }

        //  如何将订单记录存储到缓存！
        OrderRecode orderRecode = new OrderRecode();

        orderRecode.setUserId(userId);
        orderRecode.setNum(1);
        orderRecode.setOrderStr(DigestUtils.md5DigestAsHex((userId.concat(skuId.toString())).getBytes()));
        //      orderRecode.setOrderStr(MD5.encrypt(userId+skuId));
        orderRecode.setSeckillGoods(this.getSeckillGoods(skuId));

        //  将orderRecode 放入到缓存！
        //  hash key=seckill:orders field=userId value=orderRecode
        this.redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).put(userId,orderRecode);

        //  更新库存：
        this.updateStockCount(skuId);

    }

    @Override
    public Result checkOrder(Long skuId, String userId) {
        // 业务逻辑：
        //  1.判断用户是否在缓存中存在 String key = seckill:user:userId  value = skuId
        //  2.判断用户是否抢单成功 {预下单} hash  key = RedisConst.SECKILL_ORDERS field = userId value = orderRecode
        //  3.判断用户是否下过订单 {真正下过订单，还需要保存一份到缓存，还可能保存到数据库} hash  key = seckill:orders:users field = userId  value = orderId
        //  4.判断状态位
        Boolean flag = this.redisTemplate.hasKey(RedisConst.SECKILL_USER + userId);
        //  判断 flag = true; 用户在缓存中存在！
        if (flag){
            //  判断用户是否真正做了预下单！
            Boolean exist = this.redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).hasKey(userId);
            //  判断 exist = true 表示预下单成功！
            if (exist){
                OrderRecode orderRecode = (OrderRecode) this.redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).get(userId);
                //  表示第一次秒杀成功了
                return Result.build(orderRecode, ResultCodeEnum.SECKILL_SUCCESS);
            }
        }

        //  判断用户是否下过订单 ,如果用户下过订单了，则去查看我的订单！
        Boolean result = this.redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS_USERS).hasKey(userId);
        //  判断 result = true; 表示这个人已经下过订单了。
        if (result){
            String orderId = (String) this.redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS_USERS).get(userId);
            //  表示已经抢购成了并且有了订单！
            return Result.build(orderId,ResultCodeEnum.SECKILL_ORDER_SUCCESS);
        }
        //  判断一下这个状态位：
        String state = (String) CacheHelper.get(skuId.toString());
        //  判断
        if (StringUtils.isEmpty(state) || "0".equals(state)){
            return Result.build(null, ResultCodeEnum.SECKILL_FAIL);
        }
        //  默认返回值！
        return Result.build(null,ResultCodeEnum.SECKILL_RUN);
    }

    /**
     * 更新秒杀商品的库存数
     * @param skuId
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateStockCount(Long skuId) {
        //  声明一个对象
        Lock lock = new ReentrantLock();
        //  上锁
        lock.lock();
        try {
            //  存储了两份： 一份是在缓存中，一份是在数据库中！
            Long count = this.redisTemplate.opsForList().size(RedisConst.SECKILL_STOCK_PREFIX + skuId);
            //  9 update mysql redis hset key field value
            //  提升效率...
            if (count%2==0){
                SeckillGoods seckillGoodsUpd = this.getSeckillGoods(skuId);
                seckillGoodsUpd.setStockCount(count.intValue());
                //  操作mysql！
                //            SeckillGoods seckillGoods = new SeckillGoods();
                //            seckillGoods.setStockCount(count.intValue());
                //            UpdateWrapper<SeckillGoods> seckillGoodsUpdateWrapper = new UpdateWrapper<>();
                //            seckillGoodsUpdateWrapper.eq("sku_id",skuId);
                //            this.seckillGoodsMapper.update(seckillGoods,seckillGoodsUpdateWrapper);
                this.seckillGoodsMapper.updateById(seckillGoodsUpd);
                //  操作redis
                this.redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).put(skuId.toString(),seckillGoodsUpd);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //  解锁
            lock.unlock();
        }
    }

}
